/**
 * 
 */
package jvs.peer.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;

import jvs.peer.io.Channel;
import jvs.peer.io.ChannelManager;

/**
 * @author qiangli
 * 
 */
public class PeerSocket extends Socket {
	private static final ChannelManager man = ChannelManager.getInstance();
	private int receiveBufferSize = 0;
	private boolean reuseAddress = false;
	private int sendBufferSize = 0;
	private int soLinger = 0;
	private int soTimeout = 0;
	private boolean tcpNoDelay = false;
	private int trafficClass;
	private boolean bound = false;
	private boolean closed = false;
	private boolean connected = false;
	private boolean inputShutdown = false;
	private boolean outputShutdown = false;
	private boolean keepAlive = false;;
	private boolean oobInline = false;

	private OutputStream out;
	private InputStream in;

	private Channel channel = null;

	private InetAddress localAddress;
	private PeerAddress localSocketAddress;
	private PeerAddress remoteSocketAddress;

	public PeerSocket() {
	}

	public PeerSocket(String jid) throws IOException {
		this.remoteSocketAddress = new PeerAddress(jid);
		connect(remoteSocketAddress);
	}

	public PeerSocket(PeerAddress address) throws IOException {
		this.remoteSocketAddress = address;
		connect(address);
	}

	protected PeerSocket(Channel channel) throws IOException {
		this.channel = channel;
		this.in = channel.getInputStream();
		this.out = channel.getOutputStream();
		this.remoteSocketAddress = new PeerAddress(channel.getHeader()
				.getPeer());
		this.localSocketAddress = new PeerAddress(channel.getJid());
		this.connected = true;
	}

	@Override
	public void bind(SocketAddress bindpoint) throws IOException {
		this.bound = true;
		this.localSocketAddress = (PeerAddress) bindpoint;
	}

	@Override
	public synchronized void close() throws IOException {
		if (!closed) {
			closed = true;
			channel.close();
			man.remove(channel);
			channel = null;
			in = null;
			out = null;
			remoteSocketAddress = null;
			localSocketAddress = null;
		}
	}

	@Override
	public void connect(SocketAddress endpoint, int timeout) throws IOException {
		connect(endpoint);
	}

	@Override
	public void connect(SocketAddress endpoint) throws IOException {
		this.remoteSocketAddress = (PeerAddress) endpoint;
		//
		this.channel = man.openChannel(remoteSocketAddress.getId());
		this.in = channel.getInputStream();
		this.out = channel.getOutputStream();
		this.localSocketAddress = new PeerAddress(channel.getJid());
		this.connected = true;
	}

	@Override
	public SocketChannel getChannel() {
		throw new RuntimeException("Not supported. Use getPeerChannel()");
	}

	public Channel getPeerChannel() {
		return channel;
	}

	@Override
	public InetAddress getInetAddress() {
		return null;
	}

	@Override
	public InputStream getInputStream() throws IOException {
		return in;
	}

	@Override
	public boolean getKeepAlive() throws SocketException {
		return keepAlive;
	}

	@Override
	public InetAddress getLocalAddress() {
		return localAddress;
	}

	@Override
	public int getLocalPort() {
		return -1;
	}

	@Override
	public PeerAddress getLocalSocketAddress() {
		return localSocketAddress;
	}

	@Override
	public boolean getOOBInline() throws SocketException {
		return oobInline;
	}

	@Override
	public OutputStream getOutputStream() throws IOException {
		return out;
	}

	@Override
	public int getPort() {
		return -1;
	}

	@Override
	public synchronized int getReceiveBufferSize() throws SocketException {

		return receiveBufferSize;
	}

	@Override
	public PeerAddress getRemoteSocketAddress() {
		return remoteSocketAddress;
	}

	@Override
	public boolean getReuseAddress() throws SocketException {
		return reuseAddress;
	}

	@Override
	public synchronized int getSendBufferSize() throws SocketException {
		return sendBufferSize;
	}

	@Override
	public int getSoLinger() throws SocketException {
		return soLinger;
	}

	@Override
	public synchronized int getSoTimeout() throws SocketException {
		return soTimeout;
	}

	@Override
	public boolean getTcpNoDelay() throws SocketException {
		return tcpNoDelay;
	}

	@Override
	public int getTrafficClass() throws SocketException {
		return trafficClass;
	}

	@Override
	public boolean isBound() {
		return bound;
	}

	@Override
	public boolean isClosed() {
		return closed;
	}

	@Override
	public boolean isConnected() {
		return connected;
	}

	@Override
	public boolean isInputShutdown() {
		return inputShutdown;
	}

	@Override
	public boolean isOutputShutdown() {
		return outputShutdown;
	}

	@Override
	public void sendUrgentData(int data) throws IOException {
		throw new IOException("Not supported");
	}

	@Override
	public void setKeepAlive(boolean on) throws SocketException {
		this.keepAlive = on;
	}

	@Override
	public void setOOBInline(boolean on) throws SocketException {
		this.oobInline = on;
	}

	@Override
	public void setPerformancePreferences(int connectionTime, int latency,
			int bandwidth) {
		//
	}

	@Override
	public synchronized void setReceiveBufferSize(int size)
			throws SocketException {
		this.receiveBufferSize = size;
	}

	@Override
	public void setReuseAddress(boolean on) throws SocketException {
		this.reuseAddress = on;
	}

	@Override
	public synchronized void setSendBufferSize(int size) throws SocketException {
		this.sendBufferSize = size;
	}

	@Override
	public void setSoLinger(boolean on, int linger) throws SocketException {
		this.soLinger = linger;
	}

	@Override
	public synchronized void setSoTimeout(int timeout) throws SocketException {
		this.soTimeout = timeout;
	}

	@Override
	public void setTcpNoDelay(boolean on) throws SocketException {
		this.tcpNoDelay = on;
	}

	@Override
	public void setTrafficClass(int tc) throws SocketException {
		this.trafficClass = tc;
	}

	@Override
	public void shutdownInput() throws IOException {
		this.channel.shutdownInput();
		this.inputShutdown = true;
	}

	@Override
	public void shutdownOutput() throws IOException {
		this.channel.shutdownOutput();
		this.outputShutdown = true;
	}

	@Override
	public String toString() {
		return "Socket[local=" + localSocketAddress
				+ ", remote=" + remoteSocketAddress
				+ ", connected=" + isConnected() + ", closed=" + isClosed()
				+ "]";
	}
}
